package com.bone.common.annotation.aspect;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.bone.common.annotation.TokenCache;
import com.bone.common.constant.Dict;
import com.bone.common.exception.BoneException;
import com.bone.common.util.BoneUtils;
import com.bone.common.util.JwtUtil;
import com.bone.common.util.RedisUtils;
import com.bone.common.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.regex.Pattern;

/**
 * @author : WangKeKe
 * @date : 2020/10/26 17:48
 * Description : AOP刷新token关联注解 实现类  RedisCache 升级版本
 */
@Aspect
@Slf4j
@Configuration
@SuppressWarnings("all")
public class TokenCacheAspect {
    @Autowired
    private RedisUtils redisUtil;

    private static final String SEPARATOR = ":";

    /**
     * 切面缓存的生命周期
     */
    private static final Integer REDIS_TIME_OUT = 30 * 60;

    /**
     * 获取缓存验证
     */
    private static final Pattern GET_CACHE_PATTERN = Pattern.compile("^((get)|(query)|(select)|(list)).*$");

    /**
     * 更新缓存验证
     */
    private static final Pattern SET_CACHE_PATTERN = Pattern.compile("^((add)|(insert)|(save)|(update)|(delete)).*$");


    /**
     * 定义切点为缓存注解 ：如果有此 注解 则会进行AOP处理
     */
    @Pointcut("@annotation(com.bone.common.annotation.TokenCache)" + "||@within(com.bone.common.annotation.TokenCache)")
    public void queryCachePointcut() {
    }

    @Around("queryCachePointcut()")
    public Object TokenCacheAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long beginTime = System.currentTimeMillis();
        // 获取注解
        TokenCache redisCache = getAnnotation(joinPoint);

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //类路径名
        String classPathName = joinPoint.getTarget().getClass().getName();
        //类名
        String className = classPathName.substring(classPathName.lastIndexOf(".") + 1);

        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //获取方法名
        String methodName = signature.getMethod().getName();


        StringBuilder key = new StringBuilder();

        if (redisCache.setKeyPrefix().equals(Dict.SET_KEY_DEFAULT)) {
            // 没有填写前缀
            // 若无参数则 key值为 类名+方法名
            key.append(className).append(SEPARATOR).append(methodName).append(SEPARATOR);
        } else {
            // 如果填写了值 则使用注解中的值作为key的前缀
            key.append(redisCache.setKeyPrefix()).append(SEPARATOR);
        }
        if (redisCache.isParams()) {
            // 将参数加入
            //参数名
            String[] names = signature.getParameterNames();
            //参数值
            Object[] values = joinPoint.getArgs();
            keyAppendByParams(key, names, values);
        }
        // 拼接token中的内容
        keyAppendByToken(key, redisCache);

        // 如果是查询接口 则获取缓存
        if (GET_CACHE_PATTERN.matcher(methodName).matches()) {
            Object data = getObject(beginTime, joinPoint, key.toString().substring(0, key.toString().length() - 1));
            if (data != null) {
                Object parseObject = JSONObject.parseObject(JSONObject.toJSONString(data), method.getGenericReturnType());
                if (parseObject instanceof Result) {
                    ((Result) parseObject).setTimestamp(System.currentTimeMillis());
                }
                return parseObject;
            }
            return joinPoint.proceed();
            // 如果是更改或新增数据 则填充缓存
        } else if (SET_CACHE_PATTERN.matcher(methodName).matches()) {
            // 刷新缓存
            if (redisCache.setKeyPrefix().equals(Dict.SET_KEY_DEFAULT)) {
                redisUtil.delKeyByCondition(className + "*");
                log.warn("执行方法 : [ " + methodName + " ] :  清除 key 包含 [ " + className + " ] 的缓存数据");
                log.warn("AOP 清除缓存 >>>> end 耗时：" + (System.currentTimeMillis() - beginTime) + "ms.");
            } else {
                redisUtil.delKeyByCondition(redisCache.setKeyPrefix() + "*");
                log.warn("执行方法 : [ " + methodName + " ] :  清除 key 包含 [ " + redisCache.setKeyPrefix() + " ] 的缓存数据");
                log.warn("AOP 清除缓存 >>>> end 耗时：" + (System.currentTimeMillis() - beginTime) + "ms.");
            }

        }
        // 调用原始方法
        return joinPoint.proceed();
    }

    private TokenCache getAnnotation(ProceedingJoinPoint joinPoint) {
        // 获取类上的注解 - 用于获取值
        TokenCache cacheClass = joinPoint.getTarget().getClass().getAnnotation(TokenCache.class);
        if (BoneUtils.isEmpty(cacheClass)) {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取方法上的注解 -
            log.info("获取方法上的注解 ");
            return signature.getMethod().getAnnotation(TokenCache.class);
        }
        log.info("获取类上的注解");
        return cacheClass;
    }

    private void keyAppendByParams(StringBuilder key, String[] names, Object[] value) {
        if (names.length > 0) {
            StringBuilder params = new StringBuilder();
            for (int i = 0; i < names.length; i++) {
                params.append(names[i]).append(SEPARATOR).append(value[i]).append(SEPARATOR);
            }
            key.append(params).append(SEPARATOR);
        }
    }

    private void keyAppendByToken(StringBuilder key, TokenCache redisCache) {
        // 该值为true 则需要获取token中的某字段信息 作为key的一部分
        if (redisCache.isToken()) {
            String token = null;
            do {
                //获取RequestAttributes
                RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
                if (BoneUtils.isEmpty(requestAttributes)) {
                    log.error("参数：requestAttributes为空>>>>>" + DateUtil.formatDateTime(new Date()));
                    break;
                }
                //从获取RequestAttributes中获取HttpServletRequest的信息
                HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
                if (BoneUtils.isEmpty(request)) {
                    log.error("参数:request为空>>>>>>>>>>" + DateUtil.formatDateTime(new Date()));
                    break;
                }
                // 获取请求头中的token
                token = request.getHeader(Dict.X_ACCESS_TOKEN);
            } while (false);
            // 请求中的token不存在
            if (BoneUtils.isEmpty(token)) {
                throw new BoneException("注解获取得到的token值为空");
            }
            // 获取token中指定值的内容
            String info = JwtUtil.getClaim(token, redisCache.keyToken());
            if (BoneUtils.isEmpty(info)) {
                throw new BoneException(redisCache.keyToken() + "对应token中的值为空");
            }
            // 字符串拼接
            key.append(redisCache.keyToken()).append(SEPARATOR).append(info).append(SEPARATOR);
        }

    }

    /**
     * description: 使用key获取数据  不存在则查询添加
     *
     * @param beginTime : 切面开始时间
     * @param joinPoint : 切面对象
     * @param key       : 获取redis数据的key值
     * @return java.lang.Object
     **/
    private Object getObject(long beginTime, ProceedingJoinPoint joinPoint, String key) throws Throwable {
        boolean hasKey = redisUtil.hasKey(key);
        Object object;
        if (hasKey) {
            // 缓存中获取到数据，直接返回。
            object = redisUtil.get(key);
            log.warn("从缓存中获取到 key 为 [" + key + " ] : 的数据 >>>> " + object.toString());
            log.warn("AOP 获取缓存 >>>> end 耗时：" + (System.currentTimeMillis() - beginTime) + "ms.");
            return object;
        }
        // 缓存中没有数据，调用原始方法查询数据库
        object = joinPoint.proceed();
        // 设置超时时间30分钟
        redisUtil.set(key, object, REDIS_TIME_OUT);
        log.warn("向 Redis 添加 key 为 [" + key + " ] , 存活时长为 " + REDIS_TIME_OUT + " s 的数据 >>>> " + object.toString());
        log.warn("AOP 添加缓存 >>>> end 耗时：" + (System.currentTimeMillis() - beginTime) + "ms.");
        return object;
    }
}
